feat: add admin apis (CM-1235)#4211
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new set of Auth0-protected public v1 API endpoints plus supporting DAL methods to manage OSSPCKGS “stewardship” lifecycle actions (open, assign steward, escalate, update status) while writing audit entries to stewardship_activity.
Changes:
- Added DAL helpers for stewardship CRUD-ish workflows and exported shared constants for request validation.
- Added four new
/api/public/v1/stewardships/*handlers with Zod validation and wired them into the v1 router. - Added routing for the new stewardship endpoints under the existing Auth0
oauth2Middleware.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| services/libs/data-access-layer/src/osspckgs/stewardships.ts | Adds DAL APIs for opening, assigning, escalating, and status updates + shared enum constants. |
| backend/src/api/public/v1/stewardships/openStewardship.ts | New POST handler to open a package for stewardship via normalized PURL. |
| backend/src/api/public/v1/stewardships/assignSteward.ts | New PUT handler to soft-delete + insert a steward assignment. |
| backend/src/api/public/v1/stewardships/escalate.ts | New PUT handler to escalate a stewardship with a resolution path. |
| backend/src/api/public/v1/stewardships/updateStatus.ts | New PUT handler to update stewardship status with conditional inactiveReason validation. |
| backend/src/api/public/v1/stewardships/schemas.ts | Adds shared param schema for numeric stewardship id. |
| backend/src/api/public/v1/stewardships/index.ts | Adds stewardships router + rate limiting (scope checks currently commented out). |
| backend/src/api/public/v1/index.ts | Mounts the new /stewardships router under v1 with Auth0 middleware. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8af239c to
3f87f89
Compare
e1b707e to
03abe99
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 01f5fee. Configure here.
| `, | ||
| { stewardshipId, actorUserId: data.assignedBy, metadata: JSON.stringify({ status: 'assessing' }) }, | ||
| ) | ||
| if (updated) finalStewardship = mapStewardshipRow(updated) |
There was a problem hiding this comment.
moveToAssessing succeeds without status change
Medium Severity
With moveToAssessing true, the status UPDATE only applies when status is unassigned or open. If the stewardship is already past that (e.g. active), the handler still returns 200 with stewards updated but the prior status unchanged, so clients believe an atomic move to assessing occurred when it did not.
Reviewed by Cursor Bugbot for commit 01f5fee. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
backend/src/api/public/v1/packages/openapi.yaml:443
- The field-level description for
stewardsstill says "Null in v1", butGET /packages/detailcan now return an array (possibly empty) when a stewardship exists.
stewards:
description: Assigned stewards or null. Null in v1.
oneOf:
- type: array
items:
$ref: '#/components/schemas/Steward'
- type: 'null'
| SET status = 'assessing', | ||
| last_status_at = NOW(), | ||
| resolution_path = NULL, | ||
| status_note = NULL, | ||
| updated_at = NOW() | ||
| WHERE id = $(stewardshipId) | ||
| AND status IN ('unassigned', 'open') | ||
| RETURNING id, package_id, status, origin, version, opened_at, | ||
| last_status_at, inactive_reason, resolution_path, status_note, created_at, updated_at | ||
| ), |
| SET status = $(status), | ||
| last_status_at = NOW(), | ||
| inactive_reason = CASE WHEN $(status) = 'inactive' THEN $(inactiveReason) ELSE NULL END, | ||
| updated_at = NOW() |
| import { Router } from 'express' | ||
|
|
||
| import { createRateLimiter } from '@/api/apiRateLimiter' | ||
| // TODO: restore once write:stewardships is added to Auth0 staging tenant | ||
| // import { requireScopes } from '@/api/public/middlewares/requireScopes' | ||
| import { safeWrap } from '@/middlewares/errorMiddleware' | ||
|
|
||
| // import { SCOPES } from '@/security/scopes' | ||
| import { assignStewardHandler } from './assignSteward' | ||
| import { escalateHandler } from './escalate' | ||
| import { openStewardship } from './openStewardship' | ||
| import { updateStatusHandler } from './updateStatus' | ||
|
|
||
| const rateLimiter = createRateLimiter({ max: 60, windowMs: 60 * 1000 }) | ||
|
|
||
| export function stewardshipsRouter(): Router { | ||
| const router = Router() | ||
|
|
||
| router.use(rateLimiter) |
| inactiveReason: | ||
| type: | ||
| - string | ||
| - 'null' | ||
| enum: | ||
| - quarterly_cadence_missed | ||
| - stepped_down | ||
| - no_longer_critical | ||
| - 'null' |
| stewardship: | ||
| type: object | ||
| description: Stewardship state. In v1 always unassigned with no stewards or activity. | ||
| properties: |
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1c1b062 to
105ac98
Compare
| ON CONFLICT (package_id) DO UPDATE | ||
| SET status = 'open', | ||
| opened_at = NOW(), | ||
| last_status_at = NOW(), | ||
| inactive_reason = NULL, | ||
| resolution_path = NULL, | ||
| status_note = NULL, | ||
| updated_at = NOW() |
| return v instanceof Date ? v.toISOString() : String(v) | ||
| } | ||
|
|
||
| function mapStewardStewardRow(row: Record<string, unknown>): StewardshipStewardRecord { |
| router.post( | ||
| '/', | ||
| // TODO: restore once write:stewardships is added to Auth0 staging tenant | ||
| // requireScopes([SCOPES.WRITE_STEWARDSHIPS]), | ||
| safeWrap(openStewardship), |


Summary
Expose four admin-facing endpoints to support stewardship lifecycle management in the OSSPREY Program dashboard. Admins can now open a package for stewardship, assign individual stewards, escalate with a structured resolution path, and update stewardship status — all with audit logging to
stewardship_activity.Changes
POST /api/public/v1/stewardships— opens a package for stewardship (upserts status →open); body takes a PURL, normalised before DB lookupPUT /api/public/v1/stewardships/:id/steward— assigns a steward (leadorco_steward) via soft-delete + insert pattern; entire operation is wrapped in a transaction to keep the audit log consistentPUT /api/public/v1/stewardships/:id/escalate— sets status →escalatedand records one of 6 resolution paths instewardship_activitymetadataPUT /api/public/v1/stewardships/:id/status— updates stewardship status (assessing,active,needs_attention,blocked,inactive);inactiveReasonis required when settinginactiveand is preserved on other transitions (not overwritten to null)stewardships.ts) — addedgetStewardshipById,openStewardshipByPurl,assignSteward,escalateStewardship,updateStewardshipStatus; exportedESCALATION_RESOLUTION_PATHS,INACTIVE_REASONS, andSTEWARDSHIP_UPDATABLE_STATUSESas shared constantsTODOfollowing the same pattern as existing packages endpoints (restore oncewrite:stewardshipsis provisioned on the Auth0 staging tenant)Type of change
JIRA ticket
ticket
Note
High Risk
Introduces authenticated admin mutations that change stewardship state and steward assignments with scope enforcement temporarily disabled; incorrect transitions or missing authorization could affect program-critical package stewardship data.
Overview
Adds write stewardship admin APIs under
/api/v1/stewardships: open by PURL, assign stewards (optional atomic move toassessing), escalate with a resolution path, and update status (with requiredinactiveReasonforinactive). Handlers delegate to new DAL mutations that upsert/updatestewardships, managestewardship_stewards, and appendstewardship_activityaudit rows.Read paths now surface real stewardship data instead of v1 placeholders: package list/detail include
stewardshipId, stewards,lastActivityAt,resolutionPath, andstatusNote(detail loads stewards/activity viagetStewardshipSummary). OpenAPI is updated accordingly; stewarduserIdis documented as Auth0 sub.A migration adds
stewardships.resolution_pathandstatus_note.write:stewardshipsscope checks are commented out (same temporary pattern as packages) until Auth0 staging is provisioned (CM-1235).Reviewed by Cursor Bugbot for commit 7bf574d. Bugbot is set up for automated code reviews on this repo. Configure here.